﻿using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UIElements;
using winS.UnityEditor.UIElement;
using Math = winS.Unity.Math;

namespace winS.UnityEditor.ProcessEditor
{
    /// <summary>
    /// 提供对视图内元素的拖拽和对齐功能
    /// </summary>
    public class Dragger : Manipulator
    {
        public static readonly float dragThreshold = 1f;
        public static readonly float snapThreshold = 10f;
        public static readonly float snaplineOffset = 3f;
        public static readonly float snaplineLength = 500f;

        public static readonly Color spanlineColor = new Color32(63, 169, 255, 255);

        public event Action onDragEnd;
        public event Action onDragStart;

        private bool isMoved;
        private bool isEnabled;
        private bool isTopSpan;
        private bool isLeftSnap;
        private bool isRightSnap;
        private bool isBottomSnap;

        private Vector2 startPoint;
        private ElementMoveInfo receiverMoveInfo;

        private readonly List<float> xAxisSnapList = new List<float>();
        private readonly List<float> yAxisSnapList = new List<float>();
        private readonly List<ElementMoveInfo> elementMoveInfoList = new List<ElementMoveInfo>();

        private readonly VisualElement verticalSnapline;
        private readonly VisualElement horizontalSnapline;

        public Dragger(StepGraphView stepGraphView) : base(stepGraphView)
        {
            verticalSnapline = EditorElementUtility.elementFactory.CreateEditorElementWithName("垂直对齐线");
            horizontalSnapline = EditorElementUtility.elementFactory.CreateEditorElementWithName("水平对齐线");
            horizontalSnapline.style.flexDirection = FlexDirection.Row;
            verticalSnapline.style.width = horizontalSnapline.style.height = 2f;
            verticalSnapline.style.height = horizontalSnapline.style.width = snaplineLength;
            verticalSnapline.style.position = horizontalSnapline.style.position = Position.Absolute;
            verticalSnapline.style.backgroundColor = horizontalSnapline.style.backgroundColor = spanlineColor;
            verticalSnapline.visible = horizontalSnapline.visible = false;
            contentViewContainer.Add(verticalSnapline);
            contentViewContainer.Add(horizontalSnapline);

            stepGraphView.RegisterCallback<MouseUpEvent>(MouseUpCallback);
            stepGraphView.RegisterCallback<MouseDownEvent>(MouseDownCallback);
            stepGraphView.RegisterCallback<MouseMoveEvent>(MouseMoveCallback);
            stepGraphView.RegisterCallback<MouseCaptureOutEvent>(MouseCaptureOutCallback);
        }

        public void PanElement(Vector2 offset)
        {
            if (!isEnabled) return;
            foreach (var elementMoveInfo in elementMoveInfoList)
            {
                GraphElement graphElement = elementMoveInfo.graphElement;
                graphElement.position += offset;
            }
        }

        private void CollectSnapInfo()
        {
            xAxisSnapList.Clear();
            yAxisSnapList.Clear();
            foreach (var graphElement in stepGraphView.graphElements.Except(stepGraphView.selectionHashSet))
            {
                if (!graphElement.movable) continue;
                Vector2 position = graphElement.position;
                Vector2 size = graphElement.size;
                xAxisSnapList.Add(position.x);
                yAxisSnapList.Add(position.y);
                xAxisSnapList.Add(position.x + size.x);
                yAxisSnapList.Add(position.y + size.y);
            }
        }

        private void MouseUpCallback(MouseUpEvent mouseUpEvent)
        {
            if (!isEnabled) return;
            isEnabled = false;
            onDragEnd?.Invoke();
            stepGraphView.ReleaseMouse();
            verticalSnapline.visible = horizontalSnapline.visible = false;
            if (isMoved) stepGraphView.processEditor.undoSystem.AddCommand(new MoveNodeCommand(elementMoveInfoList.Select(elementMoveInfo => elementMoveInfo.Build()).ToList()));
            elementMoveInfoList.Clear();
        }
        private void MouseDownCallback(MouseDownEvent mouseDownEvent)
        {
            if (mouseDownEvent.target is not VisualElement target) return;
            if (target == stepGraphView) return;
            GraphElement receiver = target.GetFirstOfType<GraphElement>();
            if (receiver == null) return;
            if (!receiver.movable) return;
            if (!stepGraphView.selectionHashSet.Contains(receiver)) return;

            receiverMoveInfo = new ElementMoveInfo(receiver, receiver.position);
            isEnabled = true;
            isMoved = false;
            onDragStart?.Invoke();
            stepGraphView.CaptureMouse();

            startPoint = contentViewContainer.WorldToLocal(mouseDownEvent.mousePosition);

            foreach (var selection in stepGraphView.selectionHashSet)
            {
                if (!selection.movable) continue;
                elementMoveInfoList.Add(new ElementMoveInfo(selection, selection.position));
            }
            CollectSnapInfo();
        }
        private void MouseMoveCallback(MouseMoveEvent mouseMoveEvent)
        {
            if (!isEnabled) return;

            //移动距离太小,不移动
            Vector2 mouseDelta = contentViewContainer.WorldToLocal(mouseMoveEvent.mousePosition) - startPoint;
            if (mouseDelta.sqrMagnitude < dragThreshold) return;

            isMoved = true;
            verticalSnapline.visible = horizontalSnapline.visible = isLeftSnap = isRightSnap = isTopSpan = isBottomSnap = false;
            if (!mouseMoveEvent.shiftKey)
            {
                Vector2 receiverSize = receiverMoveInfo.graphElement.size;
                Vector2 receiverNewPosition = receiverMoveInfo.oldPosition + mouseDelta;
                float rightSpanPosition = receiverNewPosition.x + receiverSize.x;
                float bottomSpanPosition = receiverNewPosition.y + receiverSize.y;
                foreach (var xAxisSpan in xAxisSnapList)
                {
                    if (Math.Abs(xAxisSpan - receiverNewPosition.x) < snapThreshold)//左对齐
                    {
                        isLeftSnap = true;
                        mouseDelta.x += xAxisSpan - receiverNewPosition.x;
                        break;
                    }
                    if (Math.Abs(xAxisSpan - rightSpanPosition) < snapThreshold)//右对齐
                    {
                        isRightSnap = true;
                        mouseDelta.x += xAxisSpan - rightSpanPosition;
                        break;
                    }
                }
                foreach (var yAxisSpan in yAxisSnapList)
                {
                    if (Math.Abs(yAxisSpan - receiverNewPosition.y) < snapThreshold)//上对齐
                    {
                        isTopSpan = true;
                        mouseDelta.y += yAxisSpan - receiverNewPosition.y;
                        break;
                    }
                    if (Math.Abs(yAxisSpan - bottomSpanPosition) < snapThreshold)//下对齐
                    {
                        isBottomSnap = true;
                        mouseDelta.y += yAxisSpan - bottomSpanPosition;
                        break;
                    }
                }
            }
            foreach (var elementMoveInfo in elementMoveInfoList)
            {
                GraphElement graphElement = elementMoveInfo.graphElement;
                graphElement.position = elementMoveInfo.oldPosition + mouseDelta;
            }
            if (isLeftSnap)
            {
                verticalSnapline.visible = true;
                Vector2 position = receiverMoveInfo.currentPosition;
                position.x -= snaplineOffset;
                position.y -= snaplineLength * 0.5f - receiverMoveInfo.graphElement.size.y * 0.5f;
                verticalSnapline.SetPosition(position);
            }
            else if (isRightSnap)
            {
                verticalSnapline.visible = true;
                Vector2 position = receiverMoveInfo.currentPosition;
                position.x += receiverMoveInfo.graphElement.size.x + snaplineOffset;
                position.y -= snaplineLength * 0.5f - receiverMoveInfo.graphElement.size.y * 0.5f;
                verticalSnapline.SetPosition(position);
            }
            if (isTopSpan)
            {
                horizontalSnapline.visible = true;
                Vector2 position = receiverMoveInfo.currentPosition;
                position.y -= snaplineOffset;
                position.x -= snaplineLength * 0.5f - receiverMoveInfo.graphElement.size.x * 0.5f;
                horizontalSnapline.SetPosition(position);
            }
            else if (isBottomSnap)
            {
                horizontalSnapline.visible = true;
                Vector2 position = receiverMoveInfo.currentPosition;
                position.y += receiverMoveInfo.graphElement.size.y + snaplineOffset;
                position.x -= snaplineLength * 0.5f - receiverMoveInfo.graphElement.size.x * 0.5f;
                horizontalSnapline.SetPosition(position);
            }
        }
        private void MouseCaptureOutCallback(MouseCaptureOutEvent mouseCaptureOutEvent)
        {
            if (!isEnabled) return;
            isEnabled = false;
            onDragEnd?.Invoke();
            stepGraphView.ReleaseMouse();
            verticalSnapline.visible = horizontalSnapline.visible = false;
        }
    }
}